package net.spatula.pile;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PileIndex {

	private static final byte[] MAGIC = { (byte) 0xAB, (byte) 0xAD, (byte) 0xFA, (byte) 0xCA, (byte) 0xDE };
	private static final byte VERSION = 0x1;
	private static final String EXT = "pyx";
	
	private static final int INDEX_BUCKETS = 65536;
	private static final int OVERFLOW_BUCKETS = 1;
	private static final int BUCKET_FILES = 256;

	private static final int KEY_SIZE = 512 / 8;
	private static final int OFFSET_SIZE = 64 / 8;
	private static final int LENGTH_SIZE = 32 / 8;
	private static final int ENTRY_SIZE = KEY_SIZE + OFFSET_SIZE + LENGTH_SIZE;
	private static final int BUCKET_SIZE = ENTRY_SIZE * BUCKET_FILES;
	private static final int BUCKET_INDEX_SIZE = 4 * (INDEX_BUCKETS + OVERFLOW_BUCKETS );

	
	private static final int HEADER_LENGTH = 8;
	private static final int BUCKET_HEADER_LENGTH = 2;
	private static final int BUCKET_HEADER_SIZE = BUCKET_HEADER_LENGTH * 4;

	private RandomAccessFile indexFile;
	
	private final MessageDigest digest; 
	private static final Logger LOGGER = LoggerFactory.getLogger(Pile.class);
	
	private IntBuffer bucketMap = null;

	
	/* Index:
		 * Bytes 0-4: MAGIC
		 * Byte 5: VERSION
		 * Bytes 6-7: reserved
		 * int: count of allowed buckets
		 * int: number of allocated buckets
		 * int[65537]: ordering of index entries (initialized to all -1 for empty entries)
		 * Buckets
		 *  Entries
		 *    Bytes 0-63: key
		 *    long: index location into pile file
		 *    int: length of entry 
	*/

	public PileIndex(File file, boolean initialize) throws IOException {
		indexFile = new RandomAccessFile(file, "rw");
		indexFile.setLength(0);
		writeEmptyIndex();
		
		MessageDigest instance = null;
		try {
			instance = MessageDigest.getInstance("SHA-512");
		} catch (NoSuchAlgorithmException e) {
			
		}
		digest = instance;
		
	}


	private void writeEmptyIndex() throws IOException {
		indexFile.write(MAGIC);
		indexFile.write(VERSION);
		indexFile.write((byte)0);
		indexFile.write((byte)0);
		indexFile.writeInt(0);
		indexFile.writeInt(0);
		
		MappedByteBuffer map = indexFile.getChannel().map(MapMode.READ_WRITE, HEADER_LENGTH, BUCKET_INDEX_SIZE + BUCKET_HEADER_SIZE);
		bucketMap = map.asIntBuffer();
		bucketMap.put(0,0);
		bucketMap.put(1,0);
		for(int i = 2; i < bucketMap.capacity(); i++)
		{
			bucketMap.put(i, -1);
		}
		map.force();
	}

	long getBucketLocation(int bucketNumber)
	{
		int bucketIndex = bucketMap.get(bucketNumber);
		return (bucketIndex * BUCKET_SIZE) + BUCKET_INDEX_SIZE + HEADER_LENGTH;
	}
	
	int calculateBucketNumber(String path) {
		
		
		byte[] bytes = hash(path);

		// condense the 512 bits down to 16 bits, knowing that we will of course
		// have many more collisions
		int sum = 0;
		int bitNumber = 0;
		for (int i = 0; i < bytes.length; i += 4, bitNumber++) {
			byte thisByte = bytes[i];
			int bit = bitNumber % 8;
			int bitVal = (int) Math.pow(2, bit);
			sum <<= 1;
			if ((thisByte & bitVal) != 0)
			{
				sum |= 1;
			}
		}
		LOGGER.debug("Bucket number for " + path + " is " + (sum & 65535));
		return sum & 65535;
		
	}

	byte[] hash(String path)  {
		
		try {
			digest.reset();
			byte[] bytes = digest.digest(path.getBytes("UTF-8"));
			LOGGER.debug(path + " hashed to " + Arrays.asList(bytes));
			return bytes;
		} catch (UnsupportedEncodingException e) {
			// do nothing; unrecoverable (should never happen anyway)
		}
	
		return null;
		
	}
	
}
